#region File Description
//-----------------------------------------------------------------------------
// SkinningSample.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SkinnedModel;
using System.Collections.Generic;
#endregion

namespace CharacterGame
{
    /// <summary>
    /// Sample game showing how to display skinned character animation.
    /// </summary>
    /// 
    public class CameraObject
    {
        public float cameraArc = 0;
        public float cameraRotation = 45+90;
        public float cameraDistance = 100;

    }

    class Butterfly : SimpleAnimatedObject
    {
        float rotTime = 0;
        bool doOrbit = true;

        public void StartMotion()
        {
            doOrbit = true;
        }

        public void StopOrbit()
        {
            doOrbit = false;
        }

        public void ToggleMotion()
        {
            doOrbit = !doOrbit;
        }

        public Vector3 getWorldPosition()
        {
            return worldMatrix.Translation;
        }

        public Butterfly()
        {
            Vector3 v = new Vector3(0,40,20);
            worldMatrix.Translation += v;
        }

        public void Update(TimeSpan gameTime)
        {
            //orbit world matrix
            float t = (float) gameTime.TotalSeconds;
            rotTime += t;

            Vector3 v = new Vector3(0, 1, 0);
            if (doOrbit)
            {
                float angle = MathHelper.ToRadians(360.0f * t / 10.0f);
                Quaternion q = Quaternion.CreateFromAxisAngle(v, angle);
                worldMatrix = Matrix.Transform(worldMatrix, q);
                
                worldMatrix.M42 = 40.0f + (float) Math.Sin((double) MathHelper.ToRadians(360.0f * rotTime));
            }

            UpdatePlayer(gameTime);
        }
    }

    public class CharacterGame : Microsoft.Xna.Framework.Game
    {
        #region Fields

        GraphicsDeviceManager graphics;

        KeyboardState currentKeyboardState = new KeyboardState();
        KeyboardState prevKeyboardState = new KeyboardState();
        GamePadState currentGamePadState = new GamePadState();

        Matrix worldMatrix;
        Matrix viewMatrix;
        Matrix projectionMatrix;

        BasicEffect basicEffect;        

        CameraObject camObj = new CameraObject();
        AnimatedCharacter mainDude = new AnimatedCharacter();
        Butterfly butterfly = new Butterfly();


        //SpriteBatch spriteBatch;
        //SpriteFont Font1;
        //Vector2 FontPos;

        //line drawing mojo
        VertexBuffer vertexBuffer;
        VertexDeclaration vertexDeclaration;
        VertexPositionNormalTexture[] pointList;

        Queue<Vector3> pointQ = new Queue<Vector3>();
        Queue<Vector3> trihedralQ = new Queue<Vector3>();

        const int nPoints = 512; //this should be enough 
        short[] lineListIndices; 

        bool showWorldCoords = false;
        bool showCharacterMesh = true;
        #endregion

        #region Initialization


        public CharacterGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            
            graphics.PreferredBackBufferWidth = 853;
            graphics.PreferredBackBufferHeight = 480;

            graphics.MinimumVertexShaderProfile = ShaderProfile.VS_3_0;
            graphics.MinimumPixelShaderProfile = ShaderProfile.PS_3_0;
            lineListIndices = new short[nPoints];
            for (short i = 0; i < nPoints; i++)
            {
                lineListIndices[i] = i;
            }

        }

        private void InitializeTransform()
        {
            worldMatrix = Matrix.CreateTranslation(new Vector3(0f, 0, 0.0f));

            viewMatrix = Matrix.CreateLookAt(
                new Vector3(0.0f, 40.0f, -10.0f),
                new Vector3(0.0f, 40.0f, 0.0f),
                Vector3.Up
                );

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                (float)graphics.GraphicsDevice.Viewport.Width /
                (float)graphics.GraphicsDevice.Viewport.Height,
                1.0f, 100.0f
                );
        }

        protected override void Initialize()
        {
            InitializeTransform();
            InitializeEffect();

            pointList = new VertexPositionNormalTexture[nPoints];
            for (int i = 0; i < pointList.Length; i++)
            {
                pointList[i] = new VertexPositionNormalTexture(new Vector3(0, 0, 0), Vector3.Forward, Vector2.One);
            }
            vertexBuffer = new VertexBuffer(graphics.GraphicsDevice,
            VertexPositionNormalTexture.SizeInBytes * (pointList.Length),
            BufferUsage.None);

            // Set the vertex buffer data to the array of vertices.
            vertexBuffer.SetData<VertexPositionNormalTexture>(pointList);            

            base.Initialize();
        }

        private void InitializeEffect()
        {

            vertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionNormalTexture.VertexElements
                );

            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);

        }

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load the model.
            // Look up our custom skinning information
            // Set up Animation Clips.
            // TODO: pass in which clips to load up
#if false
            Model currentModel = Content.Load<Model>("ball");
            
            string[] tracks = new string[1];
            tracks[0] = "Take 001";
#else
            Model dudeModel = Content.Load<Model>("dudeAscii");
            
            string[] tracks = new string[4];
            
            tracks[0] = "Stand";
            tracks[1] = "Walk";
            tracks[2] = "Skip";
            tracks[3] = "Throw";
#endif
            mainDude.InitAnimation(dudeModel, ref tracks);                         
            mainDude.StartClip();

            Model butterflyModel = Content.Load<Model>("butterfly");
            tracks = new string[1];
            tracks[0] = "Take 001";

            butterfly.InitAnimation(butterflyModel, ref tracks);
            butterfly.StartClip();
            /* sprite drawing fails
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Font1 = Content.Load<SpriteFont>("Courier New");
            
            FontPos = new Vector2(graphics.GraphicsDevice.Viewport.Width / 2,
            graphics.GraphicsDevice.Viewport.Height / 2);
             */

        }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Allows the game to run logic.
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            HandleInput();  //checks if we should quit

            UpdateCamera(gameTime); //rotates camera
            
            //double d = (double) gameTime.ElapsedGameTime.Milliseconds;
            //Matrix m = Matrix.Identity;
            //m.Translation = new Vector3((float)Math.Cos(d), (float)Math.Sin(d), 10.0f); 
            //mainDude.SetMatrix(m);                // System.Math.Cos(d);
            //circleMatrix.Translation = 1.0f; // System.Math.Sin(d);


            mainDude.GetDebugPoints(ref pointQ, ref trihedralQ);

            //draws world coord frame
            //useful for debug
            if (showWorldCoords)
            {
                trihedralQ.Enqueue(new Vector3(0, 0, 0));
                trihedralQ.Enqueue(new Vector3(5, 0, 0));

                trihedralQ.Enqueue(new Vector3(0, 0, 0));
                trihedralQ.Enqueue(new Vector3(0, 5, 0));

                trihedralQ.Enqueue(new Vector3(0, 0, 0));
                trihedralQ.Enqueue(new Vector3(0, 0, 5));
            }


            mainDude.Update(gameTime.ElapsedGameTime);            
            butterfly.Update(gameTime.ElapsedGameTime);            

            base.Update(gameTime);
        }        

        /// <summary>
        /// Borken - spite drawing hoses our depth sorting of our mesh
        /// </summary>
        /// 

        /*
         * FAILS - screws up depth sorting 
        protected void drawText()
        {

            spriteBatch.Begin();

            // Draw Hello World
            string output = "Joint Angle Info:";

            // Find the center of the string
            Vector2 FontOrigin = Font1.MeasureString(output) / 2;
            // Draw the string
            spriteBatch.DrawString(Font1, output, FontPos, Color.LightGreen,
                0, FontOrigin, 1.0f, SpriteEffects.None, 1.0f);

            spriteBatch.End();

        }
        */


        ///<summary>
        ///DEBUG draw routine
        /// for drawing coord axes
        ///TODO: generalize this 
        ///</summary>
        void drawDebugLines()
        {

            graphics.GraphicsDevice.VertexDeclaration = vertexDeclaration;

            basicEffect.World = worldMatrix;
            basicEffect.View = viewMatrix;
            basicEffect.Projection = projectionMatrix;

            //Loop through pointQ and trihedralQ

            int nPoints = 0;
            while (pointQ.Count > 0)
            {
                pointList[nPoints++].Position = pointQ.Dequeue();
                pointList[nPoints++].Position = pointQ.Dequeue();

            }
            pointQ.Clear();

            int nBasicLines = nPoints / 2;
            for (int i = 0; i < nBasicLines; i++)
            {
                if (i == 0)
                {
                    basicEffect.DiffuseColor = new Vector3(0.8f, 0.8f, 0.0f);
                }
                else if (i == 1)
                {
                    basicEffect.DiffuseColor = new Vector3(0.0f, 1.0f, 1.0f);
                }
                else if (i == 2)
                {
                    basicEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 1.0f);
                }
                else
                {
                    basicEffect.DiffuseColor = new Vector3(0.0f, 0.80f, 0.0f);

                }
 
                DrawLine(i);
            }

            nPoints = 0;
            while (trihedralQ.Count > 0)
            {
                pointList[nPoints++].Position = trihedralQ.Dequeue();
                pointList[nPoints++].Position = trihedralQ.Dequeue();

                pointList[nPoints++].Position = trihedralQ.Dequeue();
                pointList[nPoints++].Position = trihedralQ.Dequeue();

                pointList[nPoints++].Position = trihedralQ.Dequeue();
                pointList[nPoints++].Position = trihedralQ.Dequeue();

            }

            trihedralQ.Clear();

            int nTris = nPoints/ 6;
            for (int i = 0; i < nTris; i++)
            {
                basicEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 0.0f);
                DrawLine(i*3);

                basicEffect.DiffuseColor = new Vector3(0.0f, 1.0f, 0.0f);
                DrawLine(i * 3 + 1);

                basicEffect.DiffuseColor = new Vector3(0.0f, 0.0f, 1.0f);
                DrawLine(i * 3 + 2);

            }
        }

        ///<summary>
        ///Draw line primitive idx
        ///not at all friendly like OpenGL
        ///</summary>
        void DrawLine(int idx)
        {
            basicEffect.Begin();

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();

                graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
                    PrimitiveType.LineList,
                    pointList,
                    0,  // vertex buffer offset to add to each element of the index buffer
                    nPoints,  // number of vertices in pointList
                    lineListIndices,  // the index buffer
                    idx*2,  // first index element to read
                    1   // number of primitives to draw
                    );

                graphics.GraphicsDevice.RenderState.FillMode = FillMode.Solid;

                pass.End();
            }

            basicEffect.End();
        }

        ///<summary>
        ///Draw line primitive idx
        ///not at all friendly like OpenGL
        ///</summary>
        void DrawLines(int nLines)
        {
            basicEffect.Begin();

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();

                graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
                    PrimitiveType.LineList,
                    pointList,
                    0,  // vertex buffer offset to add to each element of the index buffer
                    nLines *2,  // number of vertices in pointList
                    lineListIndices,  // the index buffer
                    0,  // first index element to read
                    nLines   // number of primitives to draw
                    );

                graphics.GraphicsDevice.RenderState.FillMode = FillMode.Solid;

                pass.End();
            }

            basicEffect.End();
        }
        

        ///<summary>
        /// draws character mesh
        /// relies on a 
        /// </summary>
        protected void drawCharacterMesh()
        {
            Matrix[] bones = mainDude.GetSkinTransorms();
            ModelMeshCollection meshes = mainDude.GetMeshes();
            // Render the skinned mesh.
            foreach (ModelMesh mesh in meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["Bones"].SetValue(bones);
                    effect.Parameters["View"].SetValue(viewMatrix);
                    effect.Parameters["Projection"].SetValue(projectionMatrix);
                }

                mesh.Draw();
            }                        

        }

        protected void drawButterflyMesh()
        {

            Matrix[] bones = butterfly.GetSkinTransorms();
            ModelMeshCollection meshes = butterfly.GetMeshes();
            // Render the skinned mesh.
            foreach (ModelMesh mesh in meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["Bones"].SetValue(bones);
                    effect.Parameters["View"].SetValue(viewMatrix);
                    effect.Parameters["Projection"].SetValue(projectionMatrix);
                }

                mesh.Draw();
            }     
        }


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice device = graphics.GraphicsDevice;

            device.RenderState.CullMode = CullMode.None;

            device.Clear(Color.SteelBlue);
           
            float aspectRatio = (float)device.Viewport.Width /
                    (float)device.Viewport.Height;

            // Compute camera matrices.
            //translation was 0 -40 0
            viewMatrix = Matrix.CreateTranslation(0, -40, 0) *
                          Matrix.CreateRotationY(MathHelper.ToRadians(camObj.cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(camObj.cameraArc)) *
                          Matrix.CreateLookAt(new Vector3(0, 0, -camObj.cameraDistance),
                                              new Vector3(0, 0, 0), Vector3.Up);

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    aspectRatio,1, 10000);

            drawDebugLines();
     
            if(showCharacterMesh)
                drawCharacterMesh();

            drawButterflyMesh();

            base.Draw(gameTime);
        }

        
        #endregion

        #region Handle Input


        /// <summary>
        /// Handles input for quitting the game.
        /// </summary>              
        /// 

        bool KeyPress(Keys k, KeyboardState state, KeyboardState prevState)
        {
            if (state.IsKeyDown(k) && !prevState.IsKeyDown(k))
                return true;
            else
                return false;
        }

        private void HandleInput()
        {
            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);

            if (KeyPress(Keys.F, currentKeyboardState, prevKeyboardState))
            {
                mainDude.WalkForward(-10.0f);
            }

            if (KeyPress(Keys.R, currentKeyboardState, prevKeyboardState))
            {
                mainDude.RunWalkForward(-10.0f);
            }

            if (KeyPress(Keys.R, currentKeyboardState, prevKeyboardState))
            {
                mainDude.Reset();
            }
            if (KeyPress(Keys.Space, currentKeyboardState, prevKeyboardState))
            {
                mainDude.TransitionToState(LowerState.walk);
                
            }
            // Check for exit.
            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
                currentGamePadState.Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }

            if (KeyPress(Keys.I, currentKeyboardState, prevKeyboardState))
            {

                Vector3 v = butterfly.getWorldPosition();
                mainDude.AimLowerArmAt(v);
            }

            if (KeyPress(Keys.O, currentKeyboardState, prevKeyboardState))
            {
                Vector3 v = butterfly.getWorldPosition();
                mainDude.AimWholeArmAt(v);
            }

            if (KeyPress(Keys.U, currentKeyboardState, prevKeyboardState))
            {
                Vector3 v = butterfly.getWorldPosition();
                mainDude.ReachFor(v, mainDude.boneNames.L_Clavicle,  mainDude.boneNames.L_Hand);
            }            


            if (KeyPress(Keys.J, currentKeyboardState, prevKeyboardState))
            {
                mainDude.SetUpperState(UpperState.DoNothing);
            }

            if (KeyPress(Keys.K, currentKeyboardState, prevKeyboardState))
            {
                mainDude.SetUpperState(UpperState.ReachDown);
            }

            if (KeyPress(Keys.L, currentKeyboardState, prevKeyboardState))
            {
                mainDude.SetUpperState(UpperState.Reset);
            }

            if (KeyPress(Keys.M, currentKeyboardState, prevKeyboardState))
            {
                mainDude.SetUpperState(UpperState.ReachSide);
            }

            if (KeyPress(Keys.C, currentKeyboardState, prevKeyboardState))
            {
                showCharacterMesh = !showCharacterMesh;
            }

            if (KeyPress(Keys.W, currentKeyboardState, prevKeyboardState))
            {
                showWorldCoords = !showWorldCoords;
            }

            if (KeyPress(Keys.E, currentKeyboardState, prevKeyboardState))
            {
                mainDude.DoSolve();
            }

            if (KeyPress(Keys.T, currentKeyboardState, prevKeyboardState))
            {
                Vector3 v = new Vector3(0, 1,0 );
                mainDude.AimHead(v);
            }

            if (KeyPress(Keys.G, currentKeyboardState, prevKeyboardState))
            {
                Vector3 v = new Vector3(0, 0, 1);
                mainDude.AimHead(v);
            }

            if (KeyPress(Keys.B, currentKeyboardState, prevKeyboardState))
            {

                butterfly.ToggleMotion();
            }
            prevKeyboardState = currentKeyboardState;
        }


        /// <summary>
        /// Handles camera input.
        /// </summary>
        private void UpdateCamera(GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

            // Check for input to rotate the camera up and down around the model.
            if (currentKeyboardState.IsKeyDown(Keys.Up) ||
                currentKeyboardState.IsKeyDown(Keys.W))
            {
                camObj.cameraArc += time * 0.1f;
            }
            
            if (currentKeyboardState.IsKeyDown(Keys.Down) ||
                currentKeyboardState.IsKeyDown(Keys.S))
            {
                camObj.cameraArc -= time * 0.1f;
            }

            camObj.cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * 0.25f;

            // Limit the arc movement.
            if (camObj.cameraArc > 90.0f)
                camObj.cameraArc = 90.0f;
            else if (camObj.cameraArc < -90.0f)
                camObj.cameraArc = -90.0f;

            // Check for input to rotate the camera around the model.
            if (currentKeyboardState.IsKeyDown(Keys.Right) ||
                currentKeyboardState.IsKeyDown(Keys.D))
            {
                camObj.cameraRotation += time * 0.1f;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Left) ||
                currentKeyboardState.IsKeyDown(Keys.A))
            {
                camObj.cameraRotation -= time * 0.1f;
            }

            camObj.cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * 0.25f;

            // Check for input to zoom camera in and out.
            if (currentKeyboardState.IsKeyDown(Keys.Z))
                camObj.cameraDistance += time * 0.25f;

            if (currentKeyboardState.IsKeyDown(Keys.X))
                camObj.cameraDistance -= time * 0.25f;

            camObj.cameraDistance += currentGamePadState.Triggers.Left * time * 0.5f;
            camObj.cameraDistance -= currentGamePadState.Triggers.Right * time * 0.5f;

            // Limit the camera distance.
            if (camObj.cameraDistance > 500.0f)
                camObj.cameraDistance = 500.0f;
            else if (camObj.cameraDistance < 10.0f)
                camObj.cameraDistance = 10.0f;

            if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
                currentKeyboardState.IsKeyDown(Keys.R))
            {
                camObj.cameraArc = 0;
                camObj.cameraRotation = 0;
                camObj.cameraDistance = 100;
            }
        }


        #endregion
    }


    #region Entry Point

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static class Program
    {
        static void Main()
        {
            using (CharacterGame game = new CharacterGame())
            {
                game.Run();
            }
        }
    }

    #endregion
}
